
import math
from typing import Sequence, Callable, Any, List, Tuple
import  numpy
import struct
import io

from emi.util.statistics import BoxPlot20

def write_str(output: io.BytesIO,value:str):
    if value is None:
        output.write(struct.pack("h", -1))
    else:
        _bytes = value.encode("utf8")
        _byte_size = len(_bytes)
        output.write(struct.pack("h", _byte_size))
        output.write(_bytes)

def read_str(buffer :bytes ,offset :int)->Tuple[str,int]:
    [b_size] = struct.unpack_from("h", buffer, offset)
    offset += 2
    if b_size == -1:
        return None, offset
    bytes_list = buffer[offset:offset+b_size]
    offset += b_size
    return str(bytes_list,"utf8"), offset

def write_str_list(output: io.BytesIO, values: List[str]):
    write_bytes_list(output,[ _str.encode("utf8") if _str is not None else None  for _str in values ])

def read_str_list(buffer :bytes ,offset :int)->Tuple[List[str],int]:
    bytes_list,offset = read_bytes_list(buffer,offset)
    str_list = [str(bolb,"utf8") if bolb is not None else None for bolb in bytes_list]
    return str_list, offset

def write_bytes_list(output: io.BytesIO, values: List[bytes]):
    size_list = [ -1 if value is None else len(value) for value in values]
    write_int_compress(output,size_list)

    for index,_bytes in enumerate(values):
        size = size_list[index]
        if size < 1:
            continue
        output.write(_bytes)

def read_bytes_list(buffer :bytes ,offset :int)->Tuple[List[bytes],int]:
    size_list,offset = read_int_compress(buffer,offset)
    values = []
    for i in range(0,len(size_list)):
        size = size_list[i]
        if size < 0:
            values.append(None)
        elif size == 0:
            values.append(bytes())
        else:
            item_bytes = buffer[offset:offset+size]
            offset += size
            values.append(item_bytes)
    return values,offset


def write_float_compress(output:io.BytesIO,values:[],presicion:int = 4):
    """
    压缩一序列的浮点值。
    保留的精度，默认为4
    """
    size = len(values)
    if size == 0:
        ##没有值。
        output.write(struct.pack("B", 255))
        return
    if presicion  == 0:
        ### 写入整型
        write_values = [int(v)  for v in values] ## 复制一份
    else:
        _the_precison = math.pow(10,presicion)
        write_values = [ int(v * _the_precison + 0.45) for v in values]
    size_type = 1 if size > 255 else 0  # [B or H]
    diff_type = 0
    base_type = _get_int_type_value(write_values[0])
    diff_is_all_zero = True
    for i in range(1,size):
        diff_value = write_values[i] - write_values[0]
        if diff_is_all_zero:
            diff_is_all_zero = diff_value == 0
        _new_diff_type = _get_int_type_value(diff_value)
        if _new_diff_type > diff_type:
            diff_type = _new_diff_type
        write_values[i] = diff_value
    ## 所有值一样的。
    if diff_is_all_zero:
        diff_type = 4
    ###  [DIFF_TYPE_MASK = 5] [BASE_TYPE = 4] [SIZE_TYPE = 2] = 5 * 4 * 2 = 40
    total_type = presicion * 40 + base_type * 10 + diff_type * 2 + size_type

    if total_type >=255:
        raise RuntimeError("小数点精度presicion值太大，最大最多支持6位")

    ##写入格式类型
    output.write(struct.pack("B", total_type))

    # 写入size
    if size_type == 0:
        output.write(struct.pack("B", size))
    else:
        output.write(struct.pack("H", size))
    # 写入基值
    if base_type == 0:
        output.write(struct.pack("b", write_values[0]))
    elif base_type == 1:
        output.write(struct.pack("h", write_values[0]))
    elif base_type == 2:
        output.write(struct.pack("i", write_values[0]))
    elif base_type == 3:
        output.write(struct.pack("q", write_values[0]))

    ## 写入diff值
    if diff_type == 4:
        ## 所有值跟基类是一样的。
        return
    elif diff_type == 0:
        output.write(struct.pack(f"={size-1}b", *write_values[1:]))
    elif diff_type == 1:
        output.write(struct.pack(f"={size-1}h", *write_values[1:]))
    elif diff_type == 2:
        output.write(struct.pack(f"={size-1}i", *write_values[1:]))
    elif diff_type == 3:
        output.write(struct.pack(f"={size-1}q", *write_values[1:]))


def read_float_compress(buffer :bytes ,offset :int):
    [total_type] = struct.unpack_from("B", buffer, offset)
    offset += 1
    if total_type == 255:
        return [],offset
    presicion = int(total_type / 40)
    base_type = int(total_type/ 10) % 4
    diff_type = int(total_type / 2)  % 5
    size_type = total_type % 2

    #total_type = presicion * 40 + base_type * 10 + diff_type * 2 + size_type
    # 读size
    if size_type == 0:
        [size] = struct.unpack_from("B", buffer, offset)
        offset+=1
    else:
        [size] = struct.unpack_from("H", buffer, offset)
        offset+=2

    # 读基值
    if base_type == 0:
        [base_value] = struct.unpack_from("b", buffer, offset)
        offset += 1
    elif base_type == 1:
        [base_value] = struct.unpack_from("h", buffer, offset)
        offset += 2
    elif base_type == 2:
        [base_value] = struct.unpack_from("i", buffer, offset)
        offset += 4
    elif base_type == 3:
        [base_value] = struct.unpack_from("q", buffer, offset)
        offset += 8
    else:
        raise RuntimeError("shoult not run here")

    the_precison = math.pow(10,presicion)

    # 读diff值
    values = numpy.zeros(size,float)
    values[0] = float(base_value) / the_precison
    if diff_type == 4:
        ## 所有值跟基类是一样的。
        for i in range(1,size):
            values[i] = values[0]
    elif diff_type == 0:
        diff_values = struct.unpack_from(f"={size - 1}b", buffer, offset)
        offset += ((size - 1) * 1)
        for i in range(1,size):
            values[i] = float(diff_values[i-1] + base_value) / the_precison

    elif diff_type == 1:
        diff_values = struct.unpack_from(f"={size - 1}h", buffer, offset)
        offset += ((size - 1) * 2)
        for i in range(1, size):
            values[i] = float(diff_values[i-1] + base_value) / the_precison
    elif diff_type == 2:
        diff_values = struct.unpack_from(f"={size - 1}i", buffer, offset)
        offset += ((size - 1) * 4)
        for i in range(1, size):
            values[i] = float(diff_values[i-1] + base_value) / the_precison
    elif diff_type == 3:
        diff_values = struct.unpack_from(f"={size - 1}q", buffer, offset)
        offset += ((size - 1) * 8)
        for i in range(1, size):
            values[i] = float(diff_values[i-1] + base_value) / the_precison
    return list(values),offset

def write_int_compress(output:io.BytesIO,values:[]):
    write_float_compress(output,values,presicion=0)

def read_int_compress(buffer :bytes ,offset :int):
    [total_type] = struct.unpack_from("B", buffer, offset)
    offset += 1
    if total_type == 255:
        return [],offset
    presicion = int(total_type / 40)
    base_type = int(total_type/ 10) % 4
    diff_type = int(total_type / 2)  % 5
    size_type = total_type % 2

    #total_type = presicion * 40 + base_type * 10 + diff_type * 2 + size_type
    # 读size
    if size_type == 0:
        [size] = struct.unpack_from("B", buffer, offset)
        offset+=1
    else:
        [size] = struct.unpack_from("H", buffer, offset)
        offset+=2

    # 读基值
    if base_type == 0:
        [base_value] = struct.unpack_from("b", buffer, offset)
        offset += 1
    elif base_type == 1:
        [base_value] = struct.unpack_from("h", buffer, offset)
        offset += 2
    elif base_type == 2:
        [base_value] = struct.unpack_from("i", buffer, offset)
        offset += 4
    elif base_type == 3:
        [base_value] = struct.unpack_from("q", buffer, offset)
        offset += 8
    else:
        raise RuntimeError("shoult not run here")

    assert presicion == 0


    # 读diff值
    values = numpy.zeros(size,int)
    values[0] = int(base_value)
    if diff_type == 4:
        ## 所有值跟基类是一样的。
        for i in range(1,size):
            values[i] = values[0]
    elif diff_type == 0:
        diff_values = struct.unpack_from(f"={size - 1}b", buffer, offset)
        offset += ((size - 1) * 1)
        for i in range(1,size):
            values[i] = int(diff_values[i-1] + base_value)

    elif diff_type == 1:
        diff_values = struct.unpack_from(f"={size - 1}h", buffer, offset)
        offset += ((size - 1) * 2)
        for i in range(1, size):
            values[i] = int(diff_values[i-1] + base_value)
    elif diff_type == 2:
        diff_values = struct.unpack_from(f"={size - 1}i", buffer, offset)
        offset += ((size - 1) * 4)
        for i in range(1, size):
            values[i] = int(diff_values[i-1] + base_value)
    elif diff_type == 3:
        diff_values = struct.unpack_from(f"={size - 1}q", buffer, offset)
        offset += ((size - 1) * 8)
        for i in range(1, size):
            values[i] = int(diff_values[i-1] + base_value)
    return list(values),offset



def write_boxplot20_list(output:io.BytesIO,values:List[BoxPlot20]):
    f_value_list = []
    size_list = []
    for bp in values:
        v_list = [bp.min, bp.max,
                  bp.q_5, bp.q_10,
                  bp.q_15, bp.q_20,
                  bp.q_25, bp.q_30,
                  bp.q_35, bp.q_40,
                  bp.q_45, bp.q_50,
                  bp.q_55, bp.q_60,
                  bp.q_65, bp.q_70,
                  bp.q_75, bp.q_80,
                  bp.q_85, bp.q_90,
                  bp.q_95
                  ]
        size_list.append(len(v_list))
        f_value_list.extend(v_list)
    write_int_compress(output,size_list)
    write_float_compress(output,f_value_list)

def read_boxplot20_list(buffer :bytes ,offset :int)->Tuple[List[BoxPlot20],int]:
    size_list ,offset = read_int_compress(buffer,offset)
    f_value_list,offset = read_float_compress(buffer,offset)
    bp_values = []
    _start = 0
    for size in size_list:
        v_list = f_value_list[_start:_start+size]
        bp = BoxPlot20(
                    min = v_list[0],
                    max = v_list[1],
                    q_5= v_list[2],
                    q_10=v_list[3],
                    q_15=v_list[4],
                    q_20=v_list[5],
                    q_25=v_list[6],
                    q_30=v_list[7],
                    q_35=v_list[8],
                    q_40=v_list[9],
                    q_45=v_list[10],
                    q_50=v_list[11],
                    q_55=v_list[12],
                    q_60=v_list[13],
                    q_65=v_list[14],
                    q_70=v_list[15],
                    q_75=v_list[16],
                    q_80=v_list[17],
                    q_85=v_list[18],
                    q_90=v_list[19],
                    q_95=v_list[20],
        )
        bp_values.append(bp)
        _start +=size
    return bp_values,offset


def _get_int_type_value(value):
    if value <= 127 and value >= -128:
        return 0  #stuct b 类型
    elif value <= 32767 and value >= -32768:
        return 1  #stuct h 类型
    elif value <=2147483647 and value >= -2147483648:
        return 2  # stuct i 类型
    else:
        return 3  # stuct q 类型
